package org.zhaya.sdk.builder;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.zhaya.sdk.model.PipelineModel;
import org.zhaya.sdk.model.PipelineNode;
import org.zhaya.sdk.model.ProcessorNode;
import org.zhaya.sdk.processors.condition.ProcessorEnum;
import org.zhaya.sdk.service.ProcessorManager;
import org.zhaya.sdk.util.ClonerUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author Ryan Yang
 * @Date 2022/9/16 4:27 下午
 */
public class PipelineModelBuilder {

    private static final String DODP_PIPELINE_SDK_VERSION = PipelineModelBuilder.class.getPackage().getImplementationVersion();

    /**
     * 当前操作节点
     */
    private PipelineNode currentNode;

    /**
     * 流程拆分节点
     */
    private PipelineNode ifNode;

    /**
     * 汇聚组件列表
     */
    private List<String> unionNodeIds;

    /**
     * 条件列表
     */
    private final List<ConditionsBuilder> conditionsList;

    /**
     * 条件构造器
     */
    private ConditionsBuilder conditions;

    /**
     * 节点map
     */
    private final Map<String, PipelineNode> nodeMap;

    public PipelineModelBuilder() {
        this.conditionsList = CollUtil.newArrayList();
        this.nodeMap = MapUtil.newHashMap(true);
    }

    public PipelineModelBuilder next(ProcessorNode processor) {
        if (Objects.nonNull(processor)) {
            if (nodeMap.containsKey(processor.getNodeId())) {
                return next(nodeMap.get(processor.getNodeId()));
            }
            processor.setNodeId(IdUtil.fastUUID());
        }
        addConditions(Objects.nonNull(processor) ? processor.getNodeId() : StrUtil.EMPTY);
        if (Objects.isNull(processor)) {
            return this;
        }
        PipelineNode node = new PipelineNode();
        node.setNodeId(processor.getNodeId());
        node.setConfig(JSONUtil.toJsonStr(processor.getConfig()));
        Pair<String, String> processorClassInfo = ProcessorManager.getTypeInfo(processor.getConfig().getClass().getEnclosingClass().getName());
        node.setName(processorClassInfo.getKey());
        node.setType(processorClassInfo.getValue());
        if (CollUtil.isNotEmpty(unionNodeIds)) {
            node.setParentNodeIds(unionNodeIds);
            unionNodeIds = CollUtil.newArrayList();
        } else if (Objects.isNull(currentNode)) {
            node.setParentNodeIds(CollUtil.newArrayList());
        } else {
            node.setParentNodeIds(CollUtil.newArrayList(currentNode.getNodeId()));
        }
        nodeMap.put(processor.getNodeId(), node);
        currentNode = node;
        return this;
    }

    public PipelineModelBuilder next(PipelineModelBuilder ifBuilder) {
        if (Objects.nonNull(ifBuilder.ifNode)) {
            return next(ifBuilder.ifNode);
        }
        PipelineNode ifNode = new PipelineNode();
        ifNode.setNodeId(IdUtil.fastUUID());
        ifNode.setConfig(JSONUtil.toJsonStr(Collections.singletonMap(ProcessorEnum.conditions.name(), ifBuilder.conditionsList)));
        ifNode.setType(ProcessorManager.IF_PROCESSOR_TYPE);
        ifNode.setName(ProcessorManager.IF_PROCESSOR_TYPE);
        if (Objects.isNull(currentNode)) {
            ifNode.setParentNodeIds(CollUtil.newArrayList());
        } else {
            ifNode.setParentNodeIds(CollUtil.newArrayList(currentNode.getNodeId()));
        }
        ifBuilder.ifNode = ifNode;
        addConditions(ifNode.getNodeId());
        nodeMap.put(ifNode.getNodeId(), ifNode);
        //获取流程拆分各分支头节点
        List<String> nodeIds = ifBuilder.conditionsList.stream().map(ConditionsBuilder::getNodeId).collect(Collectors.toList());
        ifBuilder.nodeMap.values().stream()
                .filter(node -> nodeIds.contains(node.getNodeId()))
                .forEach(node -> node.getParentNodeIds().add(ifNode.getNodeId()));
        nodeMap.putAll(ifBuilder.nodeMap);
        currentNode = ifBuilder.currentNode;
        return this;
    }

    private PipelineModelBuilder next(PipelineNode pipelineNode) {
        addConditions(Objects.nonNull(pipelineNode) ? pipelineNode.getNodeId() : StrUtil.EMPTY);
        if (Objects.nonNull(currentNode)) {
            pipelineNode.getParentNodeIds().add(currentNode.getNodeId());
        }
        this.currentNode = pipelineNode;
        return this;
    }

    private void addConditions(String nodeId) {
        if (Objects.nonNull(conditions)) {
            conditions.next(nodeId);
            conditionsList.add(conditions);
            conditions = null;
        }
    }

    public PipelineModelBuilder choose(ProcessorNode processorNode) {
        this.currentNode = nodeMap.get(Objects.requireNonNull(processorNode, "processor node can not be null").getNodeId());
        Objects.requireNonNull(this.currentNode, "processor node is invalid");
        return this;
    }

    public PipelineModelBuilder union(ProcessorNode... processor) {
        return union(Arrays.stream(processor).map(ProcessorNode::getNodeId).collect(Collectors.toList()));
    }

    private PipelineModelBuilder union(List<String> nodeIds) {
        if (CollUtil.isEmpty(unionNodeIds)) {
            unionNodeIds = nodeIds;
        }
        return this;
    }

    public PipelineModelBuilder when(ConditionsBuilder conditions) {
        Objects.requireNonNull(conditions, "conditions can not be null");
        if (Objects.isNull(this.conditions)) {
            this.conditions = ClonerUtils.deepClone(conditions);
            this.currentNode = null;
        }
        return this;
    }

    public PipelineModelBuilder otherwise(ProcessorNode processor) {
        return when(ConditionsBuilder.defBuilder()).next(processor);
    }

    public PipelineModelBuilder otherwise(PipelineModelBuilder ifBuilder) {
        Objects.requireNonNull(ifBuilder, "ifBuilder can not be null");
        return when(ConditionsBuilder.defBuilder()).next(ifBuilder);
    }

    public PipelineModel buildModel() {
        return PipelineModel.builder().nodes(CollUtil.newArrayList(nodeMap.values())).version(DODP_PIPELINE_SDK_VERSION).build();
    }

    public String buildModelJson() {
        return JSONUtil.toJsonStr(buildModel());
    }
}
